package com.sfc.repo.impl;

import com.sfc.cons.Constants;
import com.sfc.entity.BaseEntity;
import com.sfc.repo.BaseRepository;
import com.sfc.utils.AuthzUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.util.Assert;

import javax.persistence.Cacheable;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * Created by heylear.cai on 2015/12/28.
 */
@NoRepositoryBean
public class BaseRepositoryImpl<T extends BaseEntity, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> {

    private final EntityManager em;

    public BaseRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.em = entityManager;
    }

    public BaseRepositoryImpl(Class<T> domainClass, EntityManager em) {
        this(JpaEntityInformationSupport.getMetadata(domainClass, em), em);
    }

    @Override
    public void delete(T entity) {
        entity.setIsDelete(Constants.SC_DELETED);
        this.save(entity);
    }

    @Override
    public <S extends T> S save(S entity) {
        Assert.notNull(entity, "entity can't be null!");
        Date now = Calendar.getInstance().getTime();
        if (entity.getId() == null){
            entity.setCreateTime(now);
            entity.setCreateUser(AuthzUtils.getCurrentUserId());
            entity.setIsDelete(Constants.SC_UN_DELETE);
        }
        entity.setUpdateUser(AuthzUtils.getCurrentUserId());
        entity.setUpdateTime(now);
        return super.save(entity);
    }

    @Override
    public T findOne(final ID id) {
        return super.findOne(new Specification<T>() {
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();
                predicates.add(criteriaBuilder.equal(root.get("id"), id));
                predicates.add(criteriaBuilder.equal(root.get("isDelete"), Constants.SC_UN_DELETE));
                criteriaQuery.where(predicates.toArray(new Predicate[]{}));
                return null;
            }
        });
    }

    @Override
    public List<T> findAll() {

        CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();

        CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(getDomainClass());

        Root<T> root = criteriaQuery.from(getDomainClass());

        criteriaQuery.where(criteriaBuilder.equal(root.get("isDelete"), Constants.SC_UN_DELETE));

        Query query = em.createQuery(criteriaQuery);

        query.setHint(Constants.ORG_HIBERNATE_CACHEABLE, isCacheable());

        return query.getResultList();
    }

    @Override
    public Page<T> findAll(Pageable pageable) {
        return super.findAll(new Specification<T>() {
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();
                predicates.add(criteriaBuilder.equal(root.get("entityStatus"), 1));
                criteriaQuery.where(predicates.toArray(new Predicate[]{}));
                return null;
            }
        }, pageable);
    }

    protected boolean isCacheable() {
        return this.getDomainClass().getAnnotation(Cacheable.class) != null;
    }
}
